Introduction
With the popularity of sites such as YouTube, it seems everyone wants a "submit your own video"-type (SYOV) Web site these days. Well, it so happens that one of our clients is just one of those people. A SYOV site has a bunch of moving parts: database integration to store and fetch video info, a user creation and management system to track users, uploading modules to get the video files there in the first place, a transcoder to convert uploaded videos into a consistent format, a video player to play the videos, etc. The focus of this article is the video transcoding part of the process, in particular, maintaining source aspect ratio when the target is a fixed size.
Background
An early decision in the project was how to play all the various formats that people might upload to the site. Video can be recorded in dozens of formats, with differing levels of compression, resolution, etc. We decided early on (like almost everyone else) to centre on Flash Video (FLV). The player is ubiquitous, it offers good quality to filesize, and (importantly) there are good open source tools to handle its manipulation. The open source FFMPEG can be used to query video files and learn their details (resolution, format, etc), as well as actually perform a transcode from one format to another. The details are important, as we want to maintain the aspect ratio of the uploaded file when we transcode it. That is, if the final player is 320X240 pixels, and the submitted video is widescreen, we need to "letterbox": add black bars on the top and bottom so that the final image doesn't look "squished".
Using the Code
The code is pretty straightforward. I set this up as a Web service, so that other services and apps can easily access it. This means you can also call it asynchronously from one service or app, and let the transcoding run without slowing down the user. Note that, depending on the size of the file you wish to transcode, the process can take from seconds to minutes. On our (decent) Web server (ca. 2008), an 80meg file takes some 20 minutes. A more reasonable 6meg file takes a few seconds.
I came across a couple of problems with FFMPEG: I downloaded a precompiled binary from a 3rd party site, and found that adding padding to the video was not working as expected. It seemed to overwrite the areas of the video it was meant to be padding. I struggled with this for longer than I care to admit, certainly I must have been doing something wrong. Finally, on a hunch, I downloaded a different precompiled binary (from here) and the problem went away!
Another problem came in the capturing of output from FFMPEG to get all of the file's metadata. I created a new process and streamReader, and captured the StandardOutput from the process. Nothing. Turns out that there is no specific command in FFMPEG to read metadata from a video file, instead this info comes out as a StandardError stream when FFMPEG is called with no output file specified. Once I switched from capturing the StandardOutput to StandardError, I had no problems.
Shared Function getAllSpecs(ByVal strTheFilename As String) As String
Dim p As Process = New Process()
Dim s As String
p.StartInfo.FileName = strFfmpegPath
p.StartInfo.Arguments = "-i " & strVidsPath & strTheFilename
p.StartInfo.UseShellExecute = False
p.StartInfo.CreateNoWindow = True
p.StartInfo.RedirectStandardError = True
p.Start()
Dim sError As StreamReader = p.StandardError _
s = sError.ReadToEnd
If Not p.HasExited Then
p.Kill()
End If
sError.Close()
p.Close()
Return s
End Function
I wrote a regex to pull the wXh info out of the source file. This part of the service is not as robust as it could be. A user could, for example, upload a file named myvid_320X240_320X240.avi and bork it. Typical output from ffmpeg looks like:
FFmpeg version Sherpya-r13537, Copyright (c) 2000-2008 Fabrice Bellard, et al.
libavutil version: 49.6.0
libavcodec version: 51.57.0
libavformat version: 52.14.0
libavdevice version: 52.0.0
libavfilter version: 0.0.0
built on May 29 2008 21:35:56, gcc: 4.2.3
[avi @ 00AFDBAC]Non interleaved AVI
Input #0, avi, from 'E:\PROJECTS\media\videos\1.avi':
Duration: 00:00:27.90, start: 0.000000, bitrate: 1234 kb/s
Stream #0.0: Video: mjpeg, yuvj420p, 320x240, 15.02 tb(r)
Stream #0.1: Audio: pcm_u8, 8000 Hz, mono, 64 kb/s
Must supply at least one output file
And we need to get the 320X240 out of stream #0.0. The regex I use is given below. I am open to suggestions on how to improve this (or anything else in the code, for that matter).
Shared Function getWxH(ByVal strPassedAllSpecs As String) As vidTranscode.WxH
Dim thereturn As vidTranscode.WxH
Dim pattern1 = ".*(Stream.\#).*(Video\:).*"
Dim pattern2 As String = "[0-9]*x[0-9]\w+"
Dim matches1 As MatchCollection
Dim matches2 As MatchCollection
Dim options As RegexOptions = RegexOptions.IgnoreCase Or RegexOptions.Compiled
Dim optionRegex As New Regex(pattern1, options)
matches1 = optionRegex.Matches(strPassedAllSpecs)
If matches1.Count > 0 Then
Dim optionRegex2 As New Regex(pattern2, options)
matches2 = optionRegex2.Matches(matches1(0).Value)
If matches2.Count > 0 Then
Dim splitWxH As Array
splitWxH = Split(matches2(0).Value, "x")
thereturn.W = splitWxH(0)
thereturn.H = splitWxH(1)
Else
thereturn.W = 0
thereturn.H = 0
End If
Else
thereturn.W = 0
thereturn.H = 0
End If
Return thereturn
End Function
Next Steps
Now to work on a "handler" Web service that brings all of those things mentioned at the top of the article (file uploads, database write, this transcode, etc.) together when the user clicks on the upload button.
History
- 2nd July, 2008: Initial version